#ifndef	__CXX_VIEWS_GRPH_FONTMGR_IMPL_H__
#define	__CXX_VIEWS_GRPH_FONTMGR_IMPL_H__

#include "font.h"
#include "canvas.h"
#include "grphoal.h"
#include "std/list.h"
#include "std/avltree.h"
#include "fontcache.h"
#include "font_freetype.h"

namespace agg {

	class font_engine_freetype_int32;

	// IMPORTANT NOTICE:
	// we define a same data structure in fontcache.cpp
	// change it at the same time when you change the one here
	struct glyph_cache
	{
		avl_node_t			avlnode;
		unsigned int		glyph_code;
		unsigned int		data_size;
		unsigned int		data_type;
		double				advance_x;
		double				advance_y;
		unsigned char		data[4];
	};

}; // end of namespace agg

#define f_scale_x				(100)
#define f_subpixel_scale		(3)

namespace visgrph
{

class fontinfo_impl
{
	friend class fontinfo;
	friend class formatted_text;
	friend class fontmgr_impl;
public:
	fontinfo_impl();
	fontinfo_impl(const char* fntname, const char* filename,
		unsigned int faceid, font_charset fctype = fcs_western,
		fontstyle ftype = fts_regular);

	~fontinfo_impl();

public:
	// set the font file
	bool set_font_filename(const char* filename);
	grphfntcache::font_descriptor* get_fontdesc(void);

private:
	void init(const char* fntname, const char* filename,
		unsigned int faceid);
	void genfid(const char* fntname, const char* filename,
		unsigned int faceid, grphoal::uint128_t& fid);

public:
	fontinfo* get_fontinfo(void) {
		return &stub;
	}

	font_charset get_charset(void) {
		return m_fctype;
	}

	void* face(void) const {
		return m_face;
	}

	void face(void* f) {
		m_face = f;
	}

	char* filename(void) {
		return m_filename;
	}

	unsigned int faceid(void) {
		return m_faceid;
	}

private:
	static int avl_compare(avl_node_t* a, avl_node_t* b);

private:
	avl_node_t avlnode;
	char* m_fontname;
	char* m_filename;
	unsigned int m_faceid;
	font_charset m_fctype;
	fontstyle m_ftype;
	grphfntcache::font_descriptor* m_global_font_desc;

	void* m_face;

	// bold, italic, bold & italic
	fontinfo_impl* m_fi_b;
	fontinfo_impl* m_fi_i;
	fontinfo_impl* m_fi_bi;

	// stub
	fontinfo stub;
};

class fcache_adaptor
{
	friend class font;
public:
	fcache_adaptor();
	~fcache_adaptor();

public:
	bool init(font_impl* impl);
	bool init(fontinfo_impl* fi, unsigned int fontsize);
	grphfntcache::fcache* get_fcache(font_charset fc);

public:
	unsigned int width(void) {
		return m_fontsize >> 16;
	}

	unsigned int height(void) {
		return m_fontsize & 0xFFFF;
	}

	unsigned int fontsize(void) {
		return m_fontsize;
	}

	unsigned int fontsize(uint w, uint h) {
		if (!w) w = h;
		else if (!h) h = w;
		if (w == 0 || h == 0) return 0;
		m_fontsize = ((w & 0xFFFF) << 16) | (h & 0xFFFF);
		return m_fontsize;
	}

	fontinfo_impl* get_fontinfo(font_charset fc) {
		if (fc < 0 || fc > fcs_max)
			return NULL;
		fontinfo_impl** fi = m_finfo_ptr[fc];
		if (NULL == fi) return NULL;
		return *fi;
	}

private:
	grphfntcache::fcache* load_fcache(font_charset fcs);

private:
	fontinfo_impl* m_finfo[fcs_count];
	fontinfo_impl** m_finfo_ptr[fcs_count];

	unsigned int m_fontsize;
};

class fontmgr_impl
{
	typedef agg::font_engine_freetype_int32 font_engine_type;
public:
	fontmgr_impl();
	~fontmgr_impl();
	static fontmgr_impl* inst(void);

public:

	void lock(void);
	void unlock(void);

	// add a new font
	fontinfo_impl* add_font(const char* fntname,
		const char* filename, unsigned int faceid,
		font_charset fcs, fontstyle ftype);

	// find a font
	fontinfo_impl* find_font(const char* fntname,
		unsigned int faceid, fontstyle ftype);

	// set a default font
	bool set_default(font_charset dft, fontinfo_impl* fnt);

	// get a default font
	fontinfo_impl* get_default(font_charset dft);
	fontinfo_impl** _get_default(font_charset dft);

	// check if the font is a default and return the default type
	font_charset check_font_default(fontinfo_impl* fnt);

	// load a font fcache adaptor
	bool activate_fcache_adaptor(fcache_adaptor& fca);

	// get a glyph
	agg::glyph_cache* glyph(fcache_adaptor& fa, unsigned int glyph_code);

public:

	// add kerning
	bool add_kerning(double& x, double &y)
	{
		if (m_prev_glyph_index && m_last_glyph_index)
		{
			return m_feng.add_kerning(m_prev_glyph_index, 
				m_last_glyph_index, &x, &y);
		}
		return false;
	}

	// ascender
	double ascender(void) {
		return m_feng.ascender();
	}

	// descender
	double descender(void) {
		return m_feng.descender();
	}

	// height
	double height(void) {
		return m_feng.height();
	}

private:
	bool textout_reset(fcache_adaptor& fca);
	font_charset check_font_default_unlock(fontinfo_impl* fnt);

	// find a glyph from current activated fcache
	agg::glyph_cache* glyph(unsigned int glyph_code);

	// make the current activated charset available
	bool active_fcache(fcache_adaptor& fa, font_charset fc);

	// set the font size
	bool load_font_size(unsigned int fontsize);

private:
	font_engine_type m_feng;
	avl_node_t* m_fontinfo_tree;
	unsigned int m_prev_glyph_index;
	unsigned int m_last_glyph_index;
	fontinfo_impl* m_default_fonts[fcs_count];

	void* m_faces[fcs_count];
	grphfntcache::fcache* m_fcache[fcs_count];
	font_charset m_active_charset;
};

class font_impl
{
public:
	unsigned int fontsize;
	unsigned int font_char_spacing;
	unsigned int font_line_spacing;
	fontinfo_impl* finfo[fcs_count];

	font_impl()
		: fontsize(0)
		, font_char_spacing(0)
		, font_line_spacing(0)
	{
		fontmgr_impl *impl = fontmgr_impl::inst();
		for (int i = fcs_western; i < fcs_max; ++i)
			finfo[i] = NULL;
	}

	void char_spacing(float pix) {
		font_char_spacing = (unsigned int)(pix * 1000);
	}

	bool line_spacing(float pix) {
		unsigned int p = (unsigned int)(pix * 1000);
		if (p > 0x7FFF)
			return false;
		font_line_spacing = (0x8000 | p);
		return true;
	}

	bool line_scale(float ratio) {
		unsigned int r = (unsigned int)(ratio * 1000);
		if (r > 0x7FFF) return false;
		font_line_spacing = r;
		return true;
	}
};

#define _FLT_MAX	(3.402823466e+38F)        /* max value */

class formatted_text_impl;
struct fmt_paragraph_line
{
	uint start;
	size_t length;
	float width;		// calculated width
};

class extend_format;
class fmt_paragraph
{
public:
	fmt_paragraph(extend_format* eft);
	~fmt_paragraph() { destroy(true); }

	// get the text and size of a line
	fmt_paragraph_line* line(uint line_number)
	{
		if (line_number < first_line || line_number >= first_line + line_count)
			return NULL;
		return line_table[line_number - first_line];
	}

	void set_first_line(uint ln) {
		first_line = ln;
	}

	uint get_first_line(void) {
		return first_line;
	}

	uint get_line_count(void) {
		return line_count;
	}

	const char* text(void) {
		return content;
	}

	void destroy(bool dtor = false);
	bool paragraph_reformatting(float& width_max);
	const char* paragraph_formatting(const char* text, float& width_max);

private:
	void new_content(uint sz)
	{
		if (!sz)
		{
			if (content) delete [] content;
			content = NULL;
			content_length = 0;
			return;
		}

		// see if we can reuse the buffer
		if (content && content_length >= sz)
			return;

		content_length = sz;
		content = new char [sz + 1];
	}

	void copy_line_table(fmt_paragraph_line** tbl, uint start, uint cnt);
	void copy_extend_line_table(fmt_paragraph_line** tbl, uint start, uint cnt);
	void check_extend_line_table(void);
	void append_line(fmt_paragraph_line* line);

private:
	extend_format* extfmt;
	char* content;
	uint content_length;
	uint line_count_limit;
	uint line_count;
	uint first_line;
	fmt_paragraph_line** line_table;
};

struct extfmt_freeobject
{
	listnode_t ownerlist;
	unsigned int size;
};

struct extformat_allocator
{
	listnode_t free_fmt_paragraph_obj_list;
	listnode_t free_fmt_paragraph_line_obj_list;

	extformat_allocator();
	~extformat_allocator();
	fmt_paragraph* fmt_paragrpah_alloc(extend_format* eft);
	void fmt_paragraph_free(fmt_paragraph* p);
	fmt_paragraph_line* fmt_paragraph_line_alloc(void);
	void fmt_paragraph_line_free(fmt_paragraph_line* p);
};

struct line_iter
{
	uint line_number;
	uint paragraph;
	fmt_paragraph_line* line;
	void reset(void);
};

class extend_format : public extformat_allocator
{
	friend class fmt_paragraph;
public:
	extend_format(formatted_text_impl* fi, float required_width);
	~extend_format();

	bool reformat(void);
	bool do_format(const char* text);
	void append_paragraph(fmt_paragraph* p);

	bool line(uint line, line_iter& iter);
	bool next_line(line_iter& iter);

public:

	float required_width(float w) {
		m_required_width = w;
		return w;
	}
	
	float required_width(void) {
		return m_required_width;
	}

	uint line_count(void) {
		return m_line_count;
	}

	// get a paragraph
	fmt_paragraph* paragraph(uint idx) {
		if (idx >= m_pgph_count)
			return NULL;
		return m_pgph_table[idx];
	}

private:
	void copy_paragraph_table(fmt_paragraph** tbl, uint cnt);
	void copy_extend_paragraph_table(fmt_paragraph** tbl, uint cnt);
	void check_extend_paragraph_table(void);

	int find_paragraph_index_by_line_nubmer(uint line);

private:
	formatted_text_impl* m_fti;
	fmt_paragraph** m_pgph_table;
	uint m_pgph_count_limit;
	uint m_pgph_count;
	uint m_line_count;
	float m_required_width;
};

class formatted_text_impl
{
	friend class extend_format;
	friend class fmt_paragraph;
public:
	enum fmttxt_flags
	{
		ftf_immd_content = dt_max * 2,
		ftf_switch_formatting = dt_max * 4,
		ftf_paragraph_reformatting = dt_max * 8,
		ftf_formatted = dt_max * 16,
	};

	formatted_text_impl();
	~formatted_text_impl();

private:
	bool need_formatting(void);
	bool flags_sanity_check(void);

	/* check if we need an valid rect
	 * and if this is true, check if the
	 * rect is valid */
	bool check_rect(void);

	const char* truncate(const char* str, int sz, float& _width);

	// swith to formatting mode
	bool switch_text_formatting(void);

	// release the text no matter which mode
	// (normal / formatting) it is now
	void release_text_formatting(void);

	// set the wrapping rect
	// only set the value with input > 0
	bool set_wrapping_rect(int w, int h);

public:
	// execute the pending text formatting request
	bool execute_text_formatting(void);

	bool text(const char* content, bool bclone);

	// set the wordwrap, this may cause formatting
	bool wordwrap(bool w);

	// set the alignment (left, center, right, top, mid, bottom)
	bool set_alignment(uint flag);

public:
	bool init(font_impl* impl) {
		assert(NULL != impl);
		m_char_spacing = (unsigned short)impl->font_char_spacing;
		m_height_spacing = (unsigned short)impl->font_line_spacing;

		// initialize the font cache
		return m_fcache.init(impl);
	}

	double char_spacing(void) {
		return double(m_char_spacing) / 1000.0;
	}

	fcache_adaptor& fcache(void) { return m_fcache; }

	double height_scale(void) {
		return (m_height_spacing & 0x8000)
		? -1.f : (double(m_height_spacing) / 1000 + 1.f);
	}

	double height_spacing(void) {
		return (m_height_spacing & 0x8000)
		? (double(m_height_spacing & 0x7FFF) / 1000.0)
		: 0.0;
	}

	double line_spacing(double height) {
		double scale = height_scale();
		return (scale >= 0) ? height * scale : height + height_spacing();
	}

	void setflags(unsigned int flags) {
		flags &= 0x7FFFFFF;
		m_drawtext_flags |= flags;
	}

	void clearflags(unsigned int flags) {
		flags &= 0x7FFFFFF;
		m_drawtext_flags &= ~flags; 
	}

	unsigned int getflags(void) {
		return m_drawtext_flags & 0x7FFFFFF;
	}

	unsigned int testflags(unsigned int flags) {
		return (getflags() & flags);
	}

	bool set_drawtext_tab_length(unsigned int len) {
		if (len < 1 || len > 32)
			return false;
		--len;
		len <<= 27;
		m_drawtext_flags &= 0x7FFFFFF;
		m_drawtext_flags |= len;
		return true;
	}

	unsigned int get_drawtext_tab_length(void) {
		unsigned int ret = m_drawtext_flags >> 27;
		return ret + 1;
	}

	const char* text(void) {
		return need_formatting()
			? NULL
			: ((u.content)
				? u.content
				: "");
	}

	void text(std::string content, bool bclone) {
		text(content.c_str(), bclone);
	}

	bool wordwrap(void) {
		return m_drawtext_flags & dt_wordwrap;
	}

	const char* c_str(void) {
		if (!need_formatting())
			return (const char*)u.content;
		else return NULL;
	}

	int wrapping_width(void) {
		return m_required_width;
	}

	int wrapping_width(int w) {
		if (w > 0)
			set_wrapping_rect(w, -1);
		return w;
	}

	int wrapping_height(void) {
		return m_required_height;
	}

	int wrapping_height(int h) {
		if (h > 0)
			set_wrapping_rect(-1, h);
		return h;
	}

	extend_format* format(void) {
		if (!need_formatting())
			return NULL;
		return u.extfmting;
	}

	void wrapping_rect(int w, int h) {
		if (w <= 0 || h <= 0)
			return;
		set_wrapping_rect(w, h);
	}

	void get_wrapping_rect(int& w, int& h) {
		w = m_required_width;
		h = m_required_height;
	}

private:
	fcache_adaptor m_fcache;

	// the top 5 bits means tab length
	// all other bits has its specific meaning
	unsigned int m_drawtext_flags;
	unsigned short m_char_spacing;
	
	// if the bit16 = 1, means the (scale - 1 * 1000)
	// if the bit16 = 0, means height spacing in pixels
	unsigned short m_height_spacing;

	int m_required_width;
	int m_required_height;

	// content or extend formatting
	union {
		char* content;
		extend_format* extfmting;
	} u;
};

};	// namespace visgrph

#endif	// __CXX_VIEWS_GRPH_FONTMGR_IMPL_H__
/* EOF */

